﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace NetAssist.TruthTable
{
  /// <summary>
  /// Represents a line in a Truth Table 
  /// </summary>
  public class TruthTableItem
  {
    #region Declarations

    /// <summary>
    /// holds the state of each bit in the Truth Table line.
    /// </summary>
    private TriState[] content;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new Truth Table line item using a bitmap.
    /// </summary>
    /// <param name="value">A System.Int32 representing the bits that are on or off in the Truth Table line item.</param>
    /// <param name="length">A System.Int32 indicating the number of bits (inputs) in the Truth Table.</param>
    public TruthTableItem(int value, int length)
    {
      content = new TriState[length];

      for (int i = 0; i < length; i++)
      {
        if ((value & Table.Pow2(i)) == Table.Pow2(i))
          content[i] = TriState.True;
        else
          content[i] = TriState.False;
      }
    }

    /// <summary>
    /// /// Initializes a new Truth Table line item using existing content setting a bit to DontCare State.
    /// </summary>
    /// <param name="content">An array of LinqLib.Operators.Logical.TriState to use as the base for the new item.</param>
    /// <param name="dontCareIndex">a System.Int32 holding the index of the bit to flip.</param>
    /// <exception cref="System.IndexOutOfRangeException">dontCareIndex is out of the boundaries of the content array</exception>
    private TruthTableItem(TriState[] content, int dontCareIndex)
    {
      if (dontCareIndex >= content.Length || dontCareIndex < 0)
        throw new ArgumentOutOfRangeException("dontCareIndex", "dontCareIndex is out of content array boundary.");
      this.content = new TriState[content.Length];
      content.CopyTo(this.content, 0);
      this.content[dontCareIndex] = TriState.DontCare;
    }

    #endregion

    #region Properties

    /// <summary>
    /// DontCare is set to true if all elements in the content array are set to DontCare status
    /// </summary>
    internal bool DontCare
    {
      get { return content.All(X => X == TriState.DontCare); }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Simplifies (if possible) two truth table items.
    /// </summary>
    /// <param name="itemA">First TruthTableItem to evaluate.</param>
    /// <param name="itemB">Second TruthTableItem to evaluate.</param>
    /// <returns>A TruthTableItem that is a simplified version of ItemA and itemB. Null is returned if simplification is not possible.</returns>
    /// <exception cref="System.ArgumentNullException">
    /// itemA or itemB is null.    
    /// </exception>
    public static TruthTableItem Simplify(TruthTableItem itemA, TruthTableItem itemB)
    {
      Helper.InvalidateNullParam(itemA, "itemA");
      Helper.InvalidateNullParam(itemB, "itemB");

      TruthTableItem res = null;

      int diffIndex = -1;
      int length = itemA.content.Length;

      for (int i = 0; i < length; i++)
      {
        if (itemA.content[i] != itemB.content[i])
        {
          if (diffIndex == -1)
            diffIndex = i;
          else
          {
            diffIndex = -1;
            break;
          }
        }
      }

      if (diffIndex != -1)
        res = new TruthTableItem(itemA.content, diffIndex);

      return res;
    }

    /// <summary>
    /// Returns a Boolean string representation of the Truth Table line item.
    /// </summary>
    /// <returns>A System.String consisting of ab...f and AB...F representing false, true</returns>
    /// <remarks>
    /// 0101 will be return as AbCd, 'A' being the most significant bit, 'd' being the least significant bit.
    /// <para>Upper case denotes true and lower case denotes false.</para>
    /// <para>X1X1 will be return as AC. Don't case element are dropped out. </para>
    /// </remarks>
    public string ToBoolExpression()
    {
      StringBuilder rv = new StringBuilder();

      int length = this.content.Length;

      for (int i = 0; i < length; i++)
        switch (this.content[i])
        {
          case TriState.False:
            rv.Insert(0, (char)(length - i + 96)); //96 offset into ascii 'a'
            break;
          case TriState.True:
            rv.Insert(0, (char)(length - i + 64)); //64 offset into ascii 'A'
            break;
        }
      return rv.ToString();
    }

    /// <summary>
    /// Creates an expression representation of the line item content.
    /// </summary>
    /// <param name="parameterExpressions"></param>
    /// <returns></returns>
    internal Expression TruthTableItemExpression(ParameterExpression[] parameterExpressions)
    {
      Expression rv = null;

      int length = content.Length;

      for (int i = 0; i < length; i++)
        if (content[i] != TriState.DontCare)
        {
          Expression exp = GetBooleanExpression(content[i] == TriState.True, parameterExpressions[i]);
          if (rv == null)
            rv = exp;
          else
            rv = Expression.AndAlso(rv, exp);
        }
      return rv;
    }

    /// <summary>
    /// inverts the parameterExpression if trueElement is false.
    /// </summary>
    /// <param name="trueElement">A System.Boolean </param>
    /// <param name="parameterExpression"></param>
    /// <returns></returns>
    private static Expression GetBooleanExpression(bool trueElement, ParameterExpression parameterExpression)
    {
      if (trueElement)
        return parameterExpression;
      else
        return Expression.Not(parameterExpression);
    }

    #endregion

    #region Overrides

    /// <summary>
    /// Determines whether the specified object's state is equal to the current instance's state.
    /// </summary>
    /// <param name="obj">The object to be compared with the current instance.</param>
    /// <returns>True if the specified object is a LinqLib.Operators.Logical.TruthTableItem type and is in a state equal to the current instance's state; otherwise, false.</returns>
    public override bool Equals(object obj)
    {
      TruthTableItem x = obj as TruthTableItem;
      if (x == null)
        return false;

      return x.ToString() == this.ToString();
    }

    /// <summary>
    /// Returns the hash code for this instance's state.
    /// </summary>
    /// <returns>A 32-bit signed integer that is the hash code for this instance's state.</returns>
    public override int GetHashCode()
    {
      return this.ToString().GetHashCode();
    }

    /// <summary>
    /// Returns a string representation of the Truth Table line item.
    /// </summary>
    /// <returns>A System.String consisting of 0, 1 and X representing false, true and don't care</returns>
    public override string ToString()
    {
      StringBuilder rv = new StringBuilder();

      int length = this.content.Length;

      for (int i = 0; i < length; i++)
        switch (this.content[i])
        {
          case TriState.False:
            rv.Insert(0, "0");
            break;
          case TriState.True:
            rv.Insert(0, "1");
            break;
          case TriState.DontCare:
            rv.Insert(0, "X");
            break;
        }
      return rv.ToString();
    }

    #endregion
  }
}
